#pragma once

#include <stdio.h>
#include <iostream>
#include <SFML\Graphics.hpp>
#include "ShadowCaster.h"
#include "MultiPassRenderer.h"

#define M_PI 3.1415f
#define LIGHT_RADIUS 256.f
#define LIGHT_FOCAL_ANGLE_RAD (M_PI*2.f)
#define NB_LIGHT_VERTICES 32

void Execute()
{
	sf::RenderWindow window(sf::VideoMode(1200, 1000), "POC perfect shadow");
	//window.setFramerateLimit(60);
	window.setVerticalSyncEnabled(true);
	window.setMouseCursorVisible(false);

	struct {
		double x;
		double y;
		double direction;
		sf::Texture texture;
		sf::Sprite sprite;
		sf::Text text;
		sf::Font font;
		sf::Vertex visibleAreaVertices[7];
	} arrow;

	arrow.x = 100;
	arrow.y = 100;
	arrow.direction = 0;
	arrow.visibleAreaVertices[0].color = sf::Color(0, 0, 0, 100);
	for (int i = 1; i < 7; i++) { arrow.visibleAreaVertices[i].color = sf::Color(0, 0, 0, 50); }

	arrow.texture.loadFromFile("images/arrow.png");
	arrow.texture.setSmooth(true);
	arrow.sprite.setTexture(arrow.texture);
	arrow.sprite.setOrigin(5, 8);
	arrow.font.loadFromFile("data/OpenSans-Regular.ttf");
	arrow.text.setFont(arrow.font);
	arrow.text.setColor(sf::Color::Black);
	arrow.text.setCharacterSize(14);


	struct{
		sf::Texture texture;
		sf::Sprite sprite;
	} background;

	background.texture.loadFromFile("images/back.png");
	background.texture.setRepeated(true);
	background.sprite.setTexture(background.texture);
	background.sprite.setTextureRect(sf::IntRect(0, 0, window.getSize().x, window.getSize().y));

	struct{
		sf::RenderTexture renderTexture;
		sf::Sprite sprite;
	} scene;
	scene.renderTexture.create(window.getSize().x, window.getSize().y);
	scene.sprite.setTexture(scene.renderTexture.getTexture());

	struct{
		sf::Image image;
		sf::Texture texture;
		sf::Sprite sprite;
	} obstacles;
	obstacles.image.loadFromFile("images/obstacles.png");
	obstacles.texture.loadFromImage(obstacles.image);
	obstacles.sprite.setTexture(obstacles.texture);

	//////////////////////////
	// End of common part
	//////////////////////////


	struct{
		sf::Vertex visibleAreaVertices[NB_LIGHT_VERTICES];
		sf::RenderTexture renderTexture;
		sf::RenderStates renderStates;
		sf::Sprite sprite;
	} shadowMap;
	shadowMap.renderTexture.create(window.getSize().x, window.getSize().y);
	shadowMap.sprite.setTexture(shadowMap.renderTexture.getTexture());
	shadowMap.visibleAreaVertices[0].color = sf::Color(255, 255, 255, 255);
	for (int i = 1; i < NB_LIGHT_VERTICES; i++) { shadowMap.visibleAreaVertices[i].color = sf::Color(255, 255, 255, 0); }
	shadowMap.renderStates.blendMode = sf::BlendMultiply;

	struct{
		sf::RenderTexture renderTexture;
		sf::Shader shader;
		sf::Sprite sprite;
		sf::Texture workingArea;
		sf::Sprite workingAreaSprite;
	} passToRectangular;
	passToRectangular.renderTexture.create(LIGHT_RADIUS * 2, LIGHT_RADIUS * 2);
	passToRectangular.workingAreaSprite.setTexture(passToRectangular.workingArea);
	passToRectangular.workingAreaSprite.setTextureRect(sf::IntRect(0, 0, LIGHT_RADIUS * 2, LIGHT_RADIUS * 2));
	passToRectangular.sprite.setTexture(passToRectangular.renderTexture.getTexture());
	if (!passToRectangular.shader.loadFromFile("data/polar_to_rectangular.frag.fx", sf::Shader::Type::Fragment))
	{
		std::cerr << "error while loading shader";
		std::cin.get();
		return;
	}


	struct{
		sf::RenderTexture renderTexture1;
		sf::RenderTexture renderTexture2;
		sf::Shader shader;
		sf::Sprite sprite1;
		sf::Sprite sprite2;
		sf::Sprite workingAreaSprite;
	} passReduce;
	passReduce.renderTexture1.create(LIGHT_RADIUS * 2, LIGHT_RADIUS * 2);
	passReduce.renderTexture2.create(LIGHT_RADIUS * 2, LIGHT_RADIUS * 2);
	passReduce.workingAreaSprite.setTexture(passToRectangular.renderTexture.getTexture());
	passReduce.workingAreaSprite.setTextureRect(sf::IntRect(0, 0, LIGHT_RADIUS * 2, LIGHT_RADIUS * 2));
	passReduce.sprite1.setTexture(passReduce.renderTexture1.getTexture());
	passReduce.sprite1.setTextureRect(sf::IntRect(0, 0, LIGHT_RADIUS * 2, LIGHT_RADIUS * 2));
	passReduce.sprite2.setTexture(passReduce.renderTexture2.getTexture());
	passReduce.sprite2.setTextureRect(sf::IntRect(0, 0, LIGHT_RADIUS * 2, LIGHT_RADIUS * 2));
	if (!passReduce.shader.loadFromFile("data/horizontal_reduction_x2.frag.fx", sf::Shader::Type::Fragment))
	{
		std::cerr << "error while loading shader";
		std::cin.get();
		return;
	}
	passReduce.shader.setParameter("width", LIGHT_RADIUS * 2);

	struct{
		sf::RenderTexture renderTexture;
		sf::Shader shader;
		sf::Sprite sprite;
	} passToPolar;
	passToPolar.renderTexture.create(LIGHT_RADIUS * 2, LIGHT_RADIUS * 2);
	passToPolar.sprite.setTexture(passToPolar.renderTexture.getTexture());
	passToPolar.sprite.setTextureRect(sf::IntRect(0, 0, LIGHT_RADIUS * 2, LIGHT_RADIUS * 2));
	if (!passToPolar.shader.loadFromFile("data/rectangular_to_polar.frag.fx", sf::Shader::Type::Fragment))
	{
		std::cerr << "error while loading shader";
		std::cin.get();
		return;
	}
	passToPolar.shader.setParameter("width", LIGHT_RADIUS * 2);


	// Remark: Draw is not done properly -> should delegate more stuffs to lights
	// TODO: rework shadowcaster <-> lights 
	/*ShadowCaster shadowCaster;
	shadowCaster.draw(Light(sf::Vector2f(20.f, 20.f)));
	shadowCaster.draw(Light(sf::Vector2f(20.f, 40.f)));*/



	/////////////////////////
	// Loop
	/////////////////////////
	while (window.isOpen())
	{
		/////////////////////////
		// Common loop part
		sf::Event event;
		while (window.pollEvent(event))
		{
			if (event.type == sf::Event::Closed)
				window.close();

			if (event.type == sf::Event::MouseWheelMoved)
			{
				arrow.direction -= event.mouseWheel.delta * 0.1;
				if (arrow.direction < 0) arrow.direction += 2 * M_PI;
				else if (arrow.direction > 2 * M_PI) arrow.direction -= 2 * M_PI;
			}
		}

		sf::Vector2i mousePosition = sf::Mouse::getPosition(window);
		arrow.x = mousePosition.x;
		arrow.y = mousePosition.y;

		arrow.sprite.setPosition(arrow.x, arrow.y);
		arrow.sprite.setRotation(arrow.direction * 180.f / M_PI);
		char angleDisplay[16];
		sprintf_s(angleDisplay, "%.2f.PI rad", arrow.direction / M_PI);
		arrow.text.setString(angleDisplay);
		arrow.text.setPosition(arrow.x, arrow.y - 50);
		arrow.visibleAreaVertices[0].position = sf::Vector2f(arrow.x, arrow.y);
		for (int i = 1; i < 7; i++)
		{
			arrow.visibleAreaVertices[i].position =
				sf::Vector2f(
				arrow.x + cos(LIGHT_FOCAL_ANGLE_RAD / 5.f * (i - 1.f) - LIGHT_FOCAL_ANGLE_RAD * 0.5f + arrow.direction) * 100.f,
				arrow.y + sin(LIGHT_FOCAL_ANGLE_RAD / 5.f * (i - 1.f) - LIGHT_FOCAL_ANGLE_RAD * 0.5f + arrow.direction) * 100.f);
		}
		// End of common loop part
		//////////////////////////

		{
			if (!passToRectangular.workingArea.loadFromImage(obstacles.image, sf::IntRect(
				arrow.x - LIGHT_RADIUS,
				arrow.y - LIGHT_RADIUS,
				LIGHT_RADIUS * 2,
				LIGHT_RADIUS * 2)))
			{
				std::cerr << "error while loading obstaclesTextureExtract" << std::endl;
			}

			passToRectangular.shader.setParameter("texture", passToRectangular.workingArea);
			passToRectangular.renderTexture.clear();
			passToRectangular.renderTexture.draw(passToRectangular.workingAreaSprite, &passToRectangular.shader);
			passToRectangular.renderTexture.display();
			// miniViewMode
			passToRectangular.sprite.setPosition(window.getSize().x - LIGHT_RADIUS * 2.f, window.getSize().y - LIGHT_RADIUS * 2.f);
		}

		{
			passReduce.sprite1.setPosition(0, 0);
			passReduce.sprite2.setPosition(0, 0);
			//float width = 0.5f;

			auto workingRenderTexture = &passToRectangular.renderTexture;
			auto targettedRenderTexture = &passReduce.renderTexture1;
			auto resultingRenderTexture = &passToRectangular.renderTexture;
			for (auto currentWidth = (int)LIGHT_RADIUS * 2; currentWidth > 2; currentWidth /= 2)
			{
				targettedRenderTexture->clear();
				passReduce.shader.setParameter("texture", workingRenderTexture->getTexture());
				targettedRenderTexture->draw(passReduce.workingAreaSprite, &passReduce.shader);
				targettedRenderTexture->display();

				// switching textures
				resultingRenderTexture = targettedRenderTexture;
				targettedRenderTexture = workingRenderTexture;
				workingRenderTexture = resultingRenderTexture;
			}

			// result is in resultingRenderTexture
		//}

		//{
			passToPolar.renderTexture.clear();
			passToPolar.shader.setParameter("texture", resultingRenderTexture->getTexture());
			passToPolar.sprite.setPosition(0,0);
			passToPolar.renderTexture.draw(passToPolar.sprite, &passToPolar.shader);
		    passToPolar.renderTexture.display();
			//passToPolar.sprite.setPosition(window.getSize().x - LIGHT_RADIUS * 2.f, 0.f);
			passToPolar.sprite.setPosition(arrow.x - LIGHT_RADIUS, arrow.y - LIGHT_RADIUS);
		}

		{
			shadowMap.renderTexture.clear(sf::Color::Black);
			shadowMap.visibleAreaVertices[0].position = sf::Vector2f(arrow.x, arrow.y);
			for (int i = 1; i < NB_LIGHT_VERTICES; i++)
			{
				shadowMap.visibleAreaVertices[i].position =
					sf::Vector2f(
					arrow.x + cos(LIGHT_FOCAL_ANGLE_RAD / (NB_LIGHT_VERTICES - 2.f)* (i - 1.f) - LIGHT_FOCAL_ANGLE_RAD * 0.5f + arrow.direction) * LIGHT_RADIUS,
					arrow.y + sin(LIGHT_FOCAL_ANGLE_RAD / (NB_LIGHT_VERTICES - 2.f) * (i - 1.f) - LIGHT_FOCAL_ANGLE_RAD * 0.5f + arrow.direction) * LIGHT_RADIUS);
			}
			shadowMap.renderTexture.draw(shadowMap.visibleAreaVertices, NB_LIGHT_VERTICES, sf::TrianglesFan);
		}

		//////////////////////////
		// Final render common
		scene.renderTexture.clear(sf::Color::White);
		scene.renderTexture.draw(background.sprite);
		//scene.renderTexture.draw(obstacles.sprite);
		scene.renderTexture.display();


		window.draw(scene.sprite);
		window.draw(arrow.sprite);
		window.draw(arrow.text);
		// End of final render common
		//////////////////////////

		shadowMap.renderTexture.display();
		window.draw(shadowMap.sprite, shadowMap.renderStates);
		//window.draw(shadowCaster);
		
		passToRectangular.sprite.setPosition(arrow.x - LIGHT_RADIUS, arrow.y - LIGHT_RADIUS);
		
		passReduce.sprite2.setPosition(arrow.x - LIGHT_RADIUS, arrow.y - LIGHT_RADIUS);
		passReduce.sprite1.setPosition(arrow.x - LIGHT_RADIUS, arrow.y - LIGHT_RADIUS);
		
		window.draw(passToPolar.sprite, sf::BlendMode::BlendMultiply);
		window.draw(obstacles.sprite);

		window.display();
	}
}